package cn.jji8.patronSaint.quick.boxesMenu;

import cn.jji8.patronSaint.GlobalLoader;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.annotation.Nonnull;
import java.util.*;

/**
 * 使用菜单管理器，可以帮助你快速的创建一个菜单，无需自己编写繁琐的菜单界面了,不要使用player.openInventory()方法打开箱子，请使用本类的open(player)
 * 使用player.openInventory()会出现一些问题
 * */
public class MenuManger implements Inventory{
    static {
        GlobalLoader.onEnablTask(plugin -> Bukkit.getPluginManager().registerEvents(new Listener() {
            /**
             * 监听物品栏点击事件
             * */
            @SuppressWarnings("unused")
            @EventHandler
            public void InventoryClickEvent(InventoryClickEvent a){
                MenuManger.InventoryClickEvent(a);//传递给menuManger
            }
            /**
             * 监听关闭背包事件
             * */
            @SuppressWarnings("unused")
            @EventHandler
            public void InventoryCloseEvent(InventoryCloseEvent a){
                MenuManger.InventoryCloseEvent(a);//传递给menuManger
            }
        }, plugin));
    }
    //---------------------------------------------------------------------
    //静态
    static final Set<MenuManger> menuMangerList = new HashSet<>();
    /**
     * 监听关闭背包事件，这个方法不需要你来调用
     * */
    private static void InventoryCloseEvent(InventoryCloseEvent a) {
        MenuManger menuManger = lookForMenuMangerList(a.getInventory());
        if(menuManger!=null){
            menuManger.InventoryCloseEvents(a);
            menuMangerList.remove(menuManger);
        }
    }
    /**
     * 监听点击事件，这个方法不需要你来调用
     * */
    private static void InventoryClickEvent(InventoryClickEvent e){
        MenuManger menuManger = lookForMenuMangerList(e.getInventory());
        if(menuManger!=null){
            menuManger.InventoryClickEvents(e);
        }
    }
    /**
     * 在menuMangerMap中找到玩家对应的menuManger
     * @return  没有找到返回null
     * */
    static MenuManger lookForMenuMangerList(Inventory inventory){
        for(MenuManger menuManger:menuMangerList){
            if(menuManger.menu==inventory){
                return menuManger;
            }
        }
        return null;
    }
    //------------------------------------------------------------------------
    //实例
    Inventory menu;
    final Map<Integer,ClickImplement> SingleClickMap = new HashMap<>();
    ClickImplement WholeClick = null;
    /**
     * 获取对应的Inventory，如果需要使用player.openInventory()，请调用之前调用isOpen()
     * */
    @SuppressWarnings("unused")
    public Inventory getMenu() {
        return menu;
    }
    /**
     * 使用getMenu()获取Inventory后如果需要使用player.openInventory(Inventory)打开菜单，请在之前调用这个方法，打开多少次，调用多少次，如果调用后没有使用player.openInventory(Inventory)打开菜单，将会持续占用服务器资源。
     * */
    @SuppressWarnings("unused")
    public void isOpen(){
        menuMangerList.add(this);
    }
    /**
     * 设置菜单对应的Inventory，请不要使用null。
     * */
    @SuppressWarnings("unused")
    public void setMenu(@Nonnull Inventory menu) {
        this.menu = menu;
    }
    /**
     * 构造器，获取一个菜单管理器
     * @param size 箱子的大小，这里是排数
     * */
    @SuppressWarnings("unused")
    public MenuManger(int size){
        //创建箱子
        menu = Bukkit.createInventory(null,9*size);
    }
    /**
     * 构造器，获取一个菜单管理器
     * @param size 箱子的大小，这里是排数
     * @param name 箱子的名字。
     * */
    public MenuManger(int size, String name){
        menu = Bukkit.createInventory(null,9*size,name);
    }
    /**
     *这个箱子被点击是会被调用，这个方法不需要你来调用。
     *  */
    private void InventoryClickEvents(InventoryClickEvent e){
        if(WholeClick!=null){
            WholeClick.click(e);
        }
        ClickImplement click = SingleClickMap.get(e.getRawSlot());
        if(click!=null){
            click.click(e);
        }
    }
    /**
     * 箱子关闭时被调用，这个方法不需要你来调用。
     * */
    public void InventoryCloseEvents(InventoryCloseEvent a){
        //调用WholeClick的close()
        if(WholeClick!=null){
            WholeClick.close((Player) a.getPlayer(),menu,-1);
        }

        //调用全部点击器的close()
        Collection<Integer> clickImplementCollection = SingleClickMap.keySet();
        for(Integer clickImplement:clickImplementCollection){
            SingleClickMap.get(clickImplement).close((Player) a.getPlayer(),menu,clickImplement);
        }
    }
    /**
     * 为某个格子设置点击事件执行器，如果需要删除click可以传null
     * */
    @SuppressWarnings("unused")
    public void setSingleClick(int lattice, ClickImplement click){
        SingleClickMap.remove(lattice);
        if(click==null){
            return;
        }
        SingleClickMap.put(lattice,click);
    }
    /**
     * 设置全部的点击事件执行器，只要玩家打开了这个菜单，他的所有点击事件都会调用，如果删除掉执行器，可以传null，这个执行器最先被调用。
     * */
    @SuppressWarnings("unused")
    public void setWholeClick(ClickImplement click){
        WholeClick = click;
    }
    /**
     * 设置箱子的名字
     * */
    @SuppressWarnings("unused")
    public void setName(String name){
        int size = menu.getSize();
        Inventory newMenu = Bukkit.createInventory(null,size,name);
        for(int i=0;i<size;i++){
            newMenu.setItem(i,menu.getItem(i));
        }
        menu = newMenu;
    }
    /**
     * 给一个玩家打开这个菜单，如果玩家已经打开了这个菜单，则会刷新这个菜单
     * */
    @SuppressWarnings("unused")
    public void open(Player player){
        //调用WholeClick的open()
        if(WholeClick!=null){
            WholeClick.open(player,menu,-1);
        }
        //调用全部点击器的open()
        Collection<Integer> clickImplementCollection = SingleClickMap.keySet();
        for(Integer clickImplement:clickImplementCollection){
            SingleClickMap.get(clickImplement).open(player,menu,clickImplement);
        }
        if(!menu.getViewers().contains(player)){
            //关闭玩家之前打开的界面
            player.closeInventory();

            //将自己添加到被打开列表
            menuMangerList.add(this);

            //给玩家打开
            player.openInventory(menu);
        }
    }
    /**
     * 设置在指定索引的物品堆和点击事件执行器
     * */
    @SuppressWarnings("unused")
    public void setItemAndSingleClick(int i, @Nullable ItemStack itemStack, ClickImplement click){
        setItem(i,itemStack);
        setSingleClick(i,click);
    }

//下面是实现Inventory的方法



    /**
     * 返回此物品栏的大小.
     * */
    @Override
    public int getSize() {
        return menu.getSize();
    }

    /**
     * 返回这个物品栏的最大物品堆叠数量.
     * */
    @Override
    public int getMaxStackSize() {
        return menu.getMaxStackSize();
    }

    /**
     * 此方法可以让你改变一个物品栏的最大物品堆叠数量.
     * 警告:
     * 不是所有种类的的物品栏都遵循本值。
     * 若本值大于127，当世界保存时可能会被缩减。
     * 本值不保证被保存；一定要在设置一个格子的最大堆叠数之前修改本值
     * 若本值大于这种物品栏默认的大小，可能不会正确地在客户端上显示
     * */
    @Override
    public void setMaxStackSize(int i) {
        menu.setMaxStackSize(i);
    }

    /**
     *返回在指定索引的物品堆.
     * */
    @Override
    public @Nullable ItemStack getItem(int i) {
        return menu.getItem(i);
    }

    /**
     * 设置在指定索引的物品堆.
     * */
    @Override
    public void setItem(int i, @Nullable ItemStack itemStack) {
        menu.setItem(i,itemStack);
    }

    /**
     * 向物品栏添加所给的物品堆.本方法将尽可能完美地尝试填充已有的但还未达到堆叠上限的物品堆 和空格子.
     * 返回一个包含无法添加的物品堆的HashMap，键是传入的参数中那一物品堆所在的索引, 值是传入的可变参数中指定索引处的物品堆.如果所有物品都被添加, 将返回一个空的HashMap.
     *
     * 如果传入的物品堆超过了这一物品材质的最大堆叠数量,首先它们将被按照 物品材质的最大堆叠量，添加一部分至堆叠数量未满的物品堆.如果没有堆叠数量未满的物品堆剩余, 物品堆将被以物品栏所允许的物品堆最大堆叠数量分割, 允许超出物品材质的最大堆叠数量.
     *
     * 在对此方法的一些已知实现中，会将传入的物品堆参数的数量设置为超出而无法添加的物品堆数量.
     *
     * 译注:可能指部分实现中，此方法会直接操作传入参数对象，而不是将传入参数对象进行复制后再操作其数量
     * */
    @Override
    public @NotNull HashMap<Integer, ItemStack> addItem(@NotNull ItemStack... itemStacks) throws IllegalArgumentException {
        return menu.addItem(itemStacks);
    }

    /**
     * 移除在物品栏内的指定物品堆.
     * 本方法将按照你所给的物品堆的数量和种类作为参数尽可能多地移除物品堆.
     *
     * 返回的HashMap包含不能移除的物品堆，map的键 是你所给的参数的索引值，值是在你所给的参数指定索引处的物品堆.如果 所给的物品堆全部被移除，将返回一个空HashMap.
     * */
    @Override
    public @NotNull HashMap<Integer, ItemStack> removeItem(@NotNull ItemStack... itemStacks) throws IllegalArgumentException {
        return menu.removeItem(itemStacks);
    }

    /**
     * 返回这个物品栏内的所有物品堆.
     * @return 存储了此物品栏所有物品的数组. 个别条目可能为null
     * */
    @Override
    public @NotNull ItemStack[] getContents() {
        return menu.getContents();
    }

    /**
     * 完全地覆盖物品栏的内容.移除所有存在的内容并替换为指定的物品堆.
     * @param itemStacks  内容的完全替换;数组长度必须小于或等于getSize().
     * */
    @Override
    public void setContents(@NotNull ItemStack[] itemStacks) throws IllegalArgumentException {
        menu.setContents(itemStacks);
    }

    /**
     * 返回物品栏中可以合理存放物品的区域的内容. 大部分情况下本方法返回的内容代表整个物品栏, 但某些情况下返回的内容可能不包括盔甲或产物格子.
     * 这些内容会被用于add/contains/remove方法查找指定的物品堆.
     *
     * @return 物品栏存放的物品堆. 个别条目可能为null
     * */
    @Override
    public @NotNull ItemStack[] getStorageContents() {
        return menu.getStorageContents();
    }

    /**
     * 放置所给的物品堆至物品栏.
     * @param itemStacks  作为物品栏内容的物品堆
     * @throws IllegalArgumentException 如果数组容量超越了物品栏的容量
     * */
    @Override
    public void setStorageContents(@NotNull ItemStack[] itemStacks) throws IllegalArgumentException {
        menu.setStorageContents(itemStacks);
    }

    /**
     * 检测这个物品栏是否含有指定的物品.
     * @return 是否含有此物品
     * @throws IllegalArgumentException - 如果material为null
     * */
    @Override
    public boolean contains(@NotNull Material material) throws IllegalArgumentException {
        return menu.contains(material);
    }

    /**
     * 检测这个物品栏是否含有与给定的物品堆匹配的物品堆.
     * 当物品堆的种类和数量都匹配时才返回true.
     * @return 如果item为null返回false，如果有完全匹配的物品堆找到返回true
     * */
    @Override
    public boolean contains(@Nullable ItemStack itemStack) {
        return menu.contains(itemStack);
    }

    /**
     * 检查物品栏内是否包含指定的物品，且其数量至少满足预期值 (amount的值).
     * @param material 要检测的物品
     * @param i 物品数量最小值
     * @return 如果参数amount小于1返回true; 如果物品数量小于等于 amount 返回true
     * @throws IllegalArgumentException  如果material为null
     * */
    @Override
    public boolean contains(@NotNull Material material, int i) throws IllegalArgumentException {
        return menu.contains(material,i);
    }

    /**
     * 检测物品栏是否含有一定数量的完全匹配的物品堆.
     * 如果物品堆的种类和数量都匹配时才会计数.
     * @param itemStack 要匹配的物品
     * @param i 要检查多少相同的物品堆
     * @return item为null返回false;amount小于1返回true;如果完全匹配物品堆指定次数返回true
     * */
    @Override
    public boolean contains(@Nullable ItemStack itemStack, int i) {
        return menu.contains(itemStack,i);
    }

    /**
     * 检测物品栏是否含有与所给物品堆匹配的并数量足够的物品堆.
     * @param i 物品堆数量最小值
     * @param itemStack 要匹配的物品堆
     * @return item为null返回false;amount小于1返回true;如果物品堆的数量小于等于参数amount返回true
     * */
    @Override
    public boolean containsAtLeast(@Nullable ItemStack itemStack, int i) {
        return menu.containsAtLeast(itemStack,i);
    }

    /**
     * 查找在物品栏内的包含指定物品的所有格子与物品堆.
     * 返回的HashMap的键是格子的索引，值是在那个格子的物品堆.如果 没有找到指定的物品，将返回一个空map.
     * @return 包含格子索引和对应物品堆的map
     * @throws IllegalArgumentException  如果material为null
     * */
    @Override
    public @NotNull HashMap<Integer, ? extends ItemStack> all(@NotNull Material material) throws IllegalArgumentException {
        return menu.all(material);
    }

    /**
     * 查找在物品栏内的包含指定物品堆的所有格子与物品堆.只有 物品堆的数目和种类都相同时才会匹配.
     * 返回的HashMap的键是格子的索引，值是在那个格子的物品堆.如果 没有找到指定的物品堆，将返回一个空map.
     * @return 包含格子索引和对应物品堆的map
     * */
    @Override
    public @NotNull HashMap<Integer, ? extends ItemStack> all(@Nullable ItemStack itemStack) {
        return menu.all(itemStack);
    }

    /**
     * 查找包含此物品的第一个格子.
     * @return 包含此物品的格子序号
     * @throws IllegalArgumentException - 如果material为null
     * */
    @Override
    public int first(@NotNull Material material) throws IllegalArgumentException {
        return menu.first(material);
    }

    /**
     * 查找包含此物品堆的第一个格子。若物品堆的种类和数量都匹配才会匹配对应格子.
     * @return 给定物品堆所在的格子序号，如果未找到返回-1
     * */
    @Override
    public int first(@NotNull ItemStack itemStack) {
        return menu.first(itemStack);
    }

    /**
     * 返回第一个空格子的格子数.
     * @return 第一个空格子的格子数，-1就没有空格子
     * */
    @Override
    public int firstEmpty() {
        return menu.firstEmpty();
    }

    /**
     * 检验箱子是空地
     * */
    @Override
    public boolean isEmpty() {
        return menu.isEmpty();
    }

    /**
     * 移除在物品栏内与给定物品匹配的所有物品堆.
     * @throws IllegalArgumentException - 若material为null
     * */
    @Override
    public void remove(@NotNull Material material) throws IllegalArgumentException {
        menu.remove(material);
    }

    /**
     * 移除在此物品栏内与给定物品堆匹配的所有物品堆.
     * 只有物品堆的种类和数目相应匹配时对应的格子才会被匹配.
     * */
    @Override
    public void remove(@NotNull ItemStack itemStack) {
        menu.remove(itemStack);
    }

    /**
     * 清除单个格子.
     * */
    @Override
    public void clear(int i) {
        menu.clear(i);
    }

    /**
     * 清除整个物品栏.
     * */
    @Override
    public void clear() {
        menu.clear();
    }

    /**
     * 获得正在查看此物品栏的玩家. (以下翻译仅供参考，不保证准确性)注意:玩家被认为是在查看他们自己的物品栏 和合成屏幕甚至当所说的物品栏没有打开.他们将通常被考虑为查看他们 的物品栏甚至当他们打开了不同的物品栏，但对自定义物品栏来说有可能排除查看者 的物品栏，所以返回的内容应该从不被呈现为非空(？？？).
     * @return 正在查看此物品栏的HumanEntities列表
     * */
    @Override
    public @NotNull List<HumanEntity> getViewers() {
        return menu.getViewers();
    }

    /**
     * 返回这个物品栏的种类.
     * */
    @Override
    public @NotNull InventoryType getType() {
        //将自己添加到被打开列表
        menuMangerList.add(this);
        return menu.getType();
    }

    /**
     * 获得此物品栏的持有者(方块或实体).
     * @return 物品栏的持有者;null表示没有归属者
     * */
    @Override
    public @Nullable InventoryHolder getHolder() {
        return menu.getHolder();
    }

    /**
     * 不知道是干什么的，反正全部要重写下
     * */
    @Override
    public @NotNull ListIterator<ItemStack> iterator() {
        return menu.iterator();
    }

    /**
     *返回一个从指定索引开始的迭代器. 如果此索引是正数, 那么第一次对next()的调用将返回处在那个索引的物品; 如果索引值为负的,那么第一次对next()的调用将返回 处在索引值(getSize() + index)的物品.
     * */
    @Override
    public @NotNull ListIterator<ItemStack> iterator(int i) {
        return menu.iterator();
    }

    /**
     * 获得对应于此物品栏的方块或实体的位置. 若此容器是被定制创建的或是一个虚拟的容器/子容器时可能返回null.
     * @return 对应位置，若返回null表示此属性不适用
     * */
    @Override
    public @Nullable Location getLocation() {
        return menu.getLocation();
    }
}
